﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ColorClusteringSOM.Util;

namespace ColorClusteringSOM
{
    class TrainNetwork
    {
        private Network network;


        // radky a sloupce site
        private int rows = -1;
        private int cols = -1;

        private int epoch = -1;
        private double learningRate = -0.0;
        private double neighboorhoodRadius = -0.0;
        private double casovaKonstanta = -0.0;


        public double CasovaKonstanta
        {
            set { this.casovaKonstanta = value; }
        }

        public TrainNetwork(Network network, int epoch, double learningRate, double neighboorhoodRadius)
        {
            this.network = network;

            this.rows = network.Rows;
            this.cols = network.Cols;

            this.epoch = epoch;
            this.learningRate = learningRate;
            this.neighboorhoodRadius = neighboorhoodRadius;

            this.casovaKonstanta = epoch / Math.Log(neighboorhoodRadius);

            Console.WriteLine("Spusteni trenovani...");
        }

        /// <summary>
        /// ridici metoda, ktera spusti trenink a udela vystupni matice
        /// </summary>
        public void Train()
        {
            Random rnd = new Random();
            double[] input = new double[3];

            //start test
            List<double[]> list8 = new List<double[]>();
            list8.Add(new double[] { 255, 0, 0 });   // Red
            list8.Add(new double[] { 0, 128, 0 });   // Green
            list8.Add(new double[] { 0, 0, 255 });   // Blue
            list8.Add(new double[] { 0, 100, 0 });   // Dark green
            list8.Add(new double[] { 0, 0, 139 });   // Dark blue
            list8.Add(new double[] { 255, 255, 0 });   // Yellow
            list8.Add(new double[] { 255, 165, 0 });   // Orange
            list8.Add(new double[] { 128, 0, 128 });   // Purple
            ////end test


            //
            //list8 = Reader.ListOfColors("HtmlBarvy.txt");
            //Console.WriteLine("Barvy nacteny!");
            //

            int citacEpoch = 0;
            while (citacEpoch != epoch + 1)
            {

                double parametrUceni = learningRate * Math.Exp(-(double)citacEpoch / casovaKonstanta);
                double okoli = neighboorhoodRadius * Math.Exp(-(double)citacEpoch / casovaKonstanta);

                //if (citacEpoch % 500 == 0)
                //    Console.WriteLine("\nepocha={0}, parametrUceni={1}, okoli={2}", citacEpoch, parametrUceni, okoli);

                

                //foreach (double[] pom in list8)
                //{
                for (int i = 0; i < 100; i++)
                {
                    //input[0] = (pom[0] / 256);
                    //input[1] = (pom[1] / 256);
                    //input[2] = (pom[2] / 256);


                    input[0] = Normalization(rnd.Next(255));
                    input[1] = Normalization(rnd.Next(255));
                    input[2] = Normalization(rnd.Next(255));

                    //test 

                    double normalizer = 0;

                    double m = 0;

                    for (int j = 0; j < input.Length; j++)
                    {
                        m += (input[j] * input[j]);
                    }

                    m = Math.Sqrt(m);
                    normalizer = 1 / m;

                    for (int j = 0; j < input.Length; j++)
                    {
                        input[j] = input[j] * normalizer;
                    }





                    //test

                    //najde se vitez pro hodnotu vstupu
                    Neuron winner = Winner(input);
                    if (winner == null)
                        continue;

                    //upravi se vahy viteze a jeho okoli
                    AdjustWeights(winner, input, parametrUceni, okoli);
                    //adjustWeightsSUPERFAST(winner, input, parametrUceni, okoli);

                }

                citacEpoch++;


            }
        }


        public void Train2(double[,] matrixDATA, int rowData, int collumData)
        {

            Console.WriteLine("Start Training:" + System.DateTime.Now);
            int citacEpoch = 0;
            while (citacEpoch != epoch + 1)
            {
                //Console.WriteLine(citacEpoch);
                //Console.WriteLine("Epocha start:\t" + System.DateTime.Now);
                double parametrUceni = learningRate * Math.Exp(-(double)citacEpoch / casovaKonstanta);
                double okoli = neighboorhoodRadius * Math.Exp(-(double)citacEpoch / casovaKonstanta);

                
                //if (citacEpoch % 20 == 0)
                //{
                //    Console.WriteLine("\n TIME={0} epocha={1}, parametrUceni={2}, okoli={3}", System.DateTime.Now, citacEpoch, parametrUceni, okoli);

                //}

                for (int row = 0; row < rowData; row++)
                {
                    double[] input = new double[collumData];
                    for (int i = 0; i < collumData; i++)
                    {
                        input[i] = matrixDATA[row, i];
                    }
                    //Console.WriteLine("start:\t" + System.DateTime.Now);
                    double[] Ninput = InputNormalization(input);

                    //CheckNormality(Ninput);
                    //najde se vitez pro hodnotu vstupu
                    Neuron winner = Winner(Ninput);

                    //upravi se vahy viteze a jeho okoli
                    AdjustWeights(winner, Ninput, parametrUceni, okoli);

                    //adjustWeightsSUPERFAST(winner, input, parametrUceni, okoli);
                    input = null;
                    winner = null;

                }

                citacEpoch++;


            }
            Console.WriteLine("End Training:" + System.DateTime.Now+"\n--------------------");
        }


        public double[] InputNormalization(double[] input)
        {
            double[] result = new double[input.Length];
            double normalizer = 0;

            double m = 0;

            for (int j = 0; j < input.Length; j++)
            {
                m += (input[j] * input[j]);
            }

            m = Math.Sqrt(m);
            normalizer = 1 / m;

            for (int j = 0; j < input.Length; j++)
            {
                result[j] = input[j] * normalizer;
            }

            return result;
        }

        private void CheckNormality(double[] input)
        {
            double m = 0;

            for (int j = 0; j < input.Length; j++)
            {
                m += (input[j] * input[j]);
            }

            double result = Math.Sqrt(m);

            //if (result != 1.0)
            //{
            //    Console.WriteLine("NEROVNA SE jedne  cislo:" +result +" m=" + m);
            //}
        }

        /// <summary>
        /// Euklidovská vzdálenost
        /// </summary>
        /// <param name="input"></param>vstupni vzorek
        /// <param name="weights"></param>vahy neuronu
        /// <returns></returns>
        protected double EuklidDistance(double[] input, double[] weights)
        {
            double euDistance = 0;
            double pom = 0;

            for (int i = 0; i < input.Length; i++)
            {
                pom = input[i] - weights[i];
                euDistance += pom * pom;
            }
            return Math.Sqrt(euDistance);
        }

        /// <summary>
        /// Vrati viteze k danemu vstupu
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        protected Neuron Winner(double[] input)
        {
            // Console.Write("Finding winner...");
            Neuron winner = null;
            double euklid = double.MaxValue;

            //jen pro tisk hodnot
            int ii = 0;
            int jj = 0;

            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    Neuron actualNeuron = network[i, j];

                    //euklidovska vzdalenost : vstup a vahy neuronu
                    double actualEuklid = EuklidDistance(input, actualNeuron.Weights);
                    if (actualEuklid < euklid) // (actualEuklid <= euklid) ??? 
                    {
                        winner = actualNeuron;
                        euklid = actualEuklid;

                        ii = i;
                        jj = j;

                    }
                }
            }


            //Console.WriteLine("[ " + ii + " ][ " + jj + " ]" + winner.ToString() + " euklid:" + euklid);
            return winner;

        }


        /// <summary>
        /// Upravi vahy viteze a jeho okoli
        /// </summary>
        /// <param name="winner"></param>
        /// <param name="input"></param>
        /// <param name="learningRate"></param>
        /// <param name="neighbourhood"></param>
        protected void AdjustWeights(Neuron winner, double[] input, double learningRate, double neighbourhood)
        {
            if (winner.Weights.Length == input.Length)
            {
                // Prochazeni vsech neuronu v siti
                for (int i = 0; i < rows; i++)
                {
                    for (int j = 0; j < cols; j++)
                    {
                        Neuron neuron = this.network[i, j];
                        double xs = (neuron.X - winner.X) * (neuron.X - winner.X);
                        double ys = (neuron.Y - winner.Y) * (neuron.Y - winner.Y);



                        //  vzdalenost mezi vitezem a aktualnim neurnonem (odmocnina se nedela)
                        double distance = xs + ys;

                        // okoli se da na^2
                        double okoli2 = neighbourhood * neighbourhood;

                        if (distance < okoli2)
                        {
                            double[] neuronWeight = neuron.Weights;
                            double neighborhoodFact = NeighboorhoodFactor(winner, neuron, (2 * okoli2));

                            //upraveni vah neuronu
                            for (int w = 0; w < neuronWeight.Length; w++)
                            {
                                double inputValue = input[w];
                                double oldWeight = neuronWeight[w];

                                double newWeight = oldWeight + (learningRate * neighborhoodFact * (inputValue - oldWeight));

                                neuronWeight[w] = newWeight;

                            }
                            neuron.Weights = neuronWeight;
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("winner.Weights.Length != input.Length !!!!!");
            }
        }


        protected void adjustWeightsSUPERFAST(Neuron winner, double[] input, double parametrUceni, double okoli)
        {

            /*
              LevyHorniBod.x = vysledek.souradnice.x - (int)fi;
                    if (LevyHorniBod.x < 0) LevyHorniBod.x = 0;
                    LevyHorniBod.y = vysledek.souradnice.y - (int)fi;
                    if (LevyHorniBod.y < 0) LevyHorniBod.y = 0;
                    PravyDolniBod.x = vysledek.souradnice.x + (int)fi;
                    if (PravyDolniBod.x >= velikostVystupu.x) PravyDolniBod.x = velikostVystupu.x ;
                    PravyDolniBod.y = vysledek.souradnice.y + (int)fi;
                    if (PravyDolniBod.y >= velikostVystupu.x) PravyDolniBod.y = velikostVystupu.y ;

                    nextIteraceEu = 0;
                    for (int i = LevyHorniBody; i < PravyDolniBody; i++)
                        for (int j = LevyHorniBodx; j < PravyDolniBodx; j++)
             
             
             
             */

            int LevyHorniBodx = 0;
            int LevyHorniBody = 0;
            int PravyDolniBodx = 0;
            int PravyDolniBody = 0;

            int velikostVystupux = rows;
            int velikostVystupuy = cols;
            Neuron neuron = null;

            LevyHorniBodx = winner.X - (int)okoli;
            if (LevyHorniBodx < 0) LevyHorniBodx = 0;
            LevyHorniBody = winner.Y - (int)okoli;
            if (LevyHorniBody < 0) LevyHorniBody = 0;
            PravyDolniBodx = winner.X + (int)okoli;
            if (PravyDolniBodx >= velikostVystupux) PravyDolniBodx = velikostVystupux;
            PravyDolniBody = winner.Y + (int)okoli;
            if (PravyDolniBody >= velikostVystupux) PravyDolniBody = velikostVystupuy;
            //if (zmena != (PravyDolniBody - LevyHorniBody))
            //{
            //    Console.WriteLine("Velikost ctverce\nstrana X=" + (PravyDolniBody - LevyHorniBody) + "\nY=" + (PravyDolniBodx - LevyHorniBodx));
            //    zmena = (PravyDolniBody - LevyHorniBody);
            //}

            if (winner.Weights.Length == input.Length)
            {
                // Prochazeni Ctverce site kolem viteze podle okoli
                for (int i = LevyHorniBody; i < PravyDolniBody; i++)
                {
                    for (int j = LevyHorniBodx; j < PravyDolniBodx; j++)
                    {
                        neuron = this.network[i, j];

                        double[] neuronWeight = neuron.Weights;
                        double neighborhoodFact = NeighboorhoodFactor(winner, neuron, (2 * (okoli * okoli)));

                        double paNe = parametrUceni * neighborhoodFact;
                        //upraveni vah neuronu
                        for (int w = 0; w < neuronWeight.Length; w++)
                        {
                            double inputValue = input[w];
                            double oldWeight = neuronWeight[w];


                            double newWeight = oldWeight + paNe * (inputValue - oldWeight);

                            neuronWeight[w] = newWeight;

                            //if (oldWeight != newWeight)
                            //    Console.WriteLine("Oldweight=" + oldWeight + "\t newWeight=" + newWeight);

                        }
                        neuron.Weights = neuronWeight;

                    }
                }
            }
            else
            {
                Console.WriteLine("winner.Weights.Length != input.Length !!!!!");
            }


        }


        /// <summary>
        /// Vrati hodnotu okoli neboli omega
        /// </summary>
        /// <param name="winner"></param>
        /// <param name="actual"></param>
        /// <param name="okoli"></param>
        /// <returns></returns>
        protected double NeighboorhoodFactor(Neuron winner, Neuron actual, double dvaKratokoliNA2)
        {
            // double omega = Math.Exp(-(double)euklid / (2 * fiNa2));
            double xs = (actual.X - winner.X) * (actual.X - winner.X);
            double ys = (actual.Y - winner.Y) * (actual.Y - winner.Y);

            //  vzdalenost mezi vitezem a aktualnim neurnonem
            // double distance = Math.Sqrt(xs + ys);

            double distance = xs + ys;
            double result = Math.Exp(-(distance) / (dvaKratokoliNA2));

            //double distance2 = Math.Sqrt(xs + ys);
            //double result2 = Math.Exp(-(distance2 * distance2) / (dvaKratokoliNA2));

            //double result = Math.Exp(-(distance) / (2 * (okoli * okoli)));




            return result;
        }

        /**
         int rozdilX = souradnice.x - souradiceVyherce.x;
            int rozdilY = souradnice.y - souradiceVyherce.y;
            //Nedelam odmocninu protoze pro dalsi vypocty potrebuji mit euklida ^2
            float euklid = rozdilX * rozdilX + rozdilY * rozdilY;

            if (euklid < fiNa2)
            {
                //    bool Cosine = true;
                //     if ((iterace % LIMITITERACE) == 0)
                //        Cosine = false;
                int counter = 0;
                int maxCounte = seznamNenul.Length;
                double omega = Math.Exp(-(double)euklid / (2 * fiNa2));
                omega *= uciciPomer;     //zmena
                int pocet = vahy.Length;
         
         
         */


        /// <summary>
        /// TODO: metoda je presunuta do Helper
        /// RGB
        /// Normalizuje vstup na hodnotz na rozsah[0,1]
        ///* N = (input - Vmin)/(Vmax - Vmin)
        ///* N = (input - 0)/(255 - 0)
        ///* Vmin = 0
        ///* Vmax = 255
        ///*/
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        protected double Normalization(int input)
        {
            double Dinput = input;
            // Console.WriteLine("input=" + input + "   Dinput/255=" + Dinput / 255);
            return (Dinput / 256);
        }

    }
}
